import pygame
import pygame._view  ## required for the py2exe script =/
import random, math

TILE_SIZE      = 16   ## pixels per tile
H_TILES        = 30  ## how may horizontal tiles
V_TILES        = 20  ## how many vertical tiles
PLAY_AREA_CLEAR_COLOR = (255,255,255) ## white
UI_CLEAR_COLOR = (0,0,0)
MAX_FPS        = 60
TICKS_PER_SECOND=80
WIN_PERCENT    = 75
##FONT_NAME      = 'PCSeniorRegular.ttf'
BALL_FILE      = 'ball16.png'
BACKGROUND_FILE= "background.png"
FONT_SIZE_SMALL= 25
TEXT_COLOR     = (255,255,255)
BLOCK_COLOR    = (0,0,0) ## the fill color of the tiles
BALL_SPEED     = 200  ## px per second
RAY_SPEED      = 300 ## px per second
RAY_COLOR      = [(255,0,0),(0,0,255)] ## red ray, blue ray
BORDER_WIDTH   = 25 ## on the sides, px
FOOTER_HEIGHT  = 50 ## bottom, px
HEADER_HEIGHT  = 50 ## top, px
FOOTER_WIDTH   = BORDER_WIDTH*2 + H_TILES*TILE_SIZE
HEADER_WIDTH   = BORDER_WIDTH*2 + H_TILES*TILE_SIZE
SCREEN_SIZE    = (BORDER_WIDTH*2 + H_TILES*TILE_SIZE,
                  FOOTER_HEIGHT + FOOTER_HEIGHT + V_TILES*TILE_SIZE)
SCORE_MULTIPLIER = 100
BONUS_MULTIPLIER = 250

HORIZONTAL_ARROW =(
"                                ",
"                                ",
"                                ",
"        XX            XX        ",
"      XXXX            XXXX      ",
"    XXX..X            X..XXX    ",
"  XXX....XXXXXXXXXXXXXX....XXX  ",
"XXX..........................XXX",
"XXX..........................XXX",
"  XXX....XXXXXXXXXXXXXX....XXX  ",
"    XXX..X            X..XXX    ",
"      XXXX            XXXX      ",
"        XX            XX        ",
"                                ",
"                                ",
"                                "
)


VERTICAL_ARROW = [ [' ']*len(HORIZONTAL_ARROW) for i in range(0,len(HORIZONTAL_ARROW[0]))]

for r in range(0,len(HORIZONTAL_ARROW)):
    for c in range(0,len(HORIZONTAL_ARROW[0])):
        VERTICAL_ARROW[c][r] = HORIZONTAL_ARROW[r][c]

class Matrix:
    def __init__(self,width,height):
        self.width  = width
        self.height = height
        self.data = [ False for i in range(width*height) ]

    def set(self,row,col,data):
        if row < 0 or row > self.height or col < 0 or col > self.width:
            raise IndexError("(%s,%s) out of bounds"%(row,col))
        self.data[self.width*row + col] = data

    def get(self,row,col):
        if row < 0 or row > self.height or col < 0 or col > self.width:
            raise IndexError("(%s,%s) out of bounds"%(row,col))
        return self.data[self.width*row + col]

    def count(self,value):
        return self.data.count(value)

class State:
    EXITING   = 0
    RUNNING   = 1
    START_UP  = 2
    LEVEL_WIN = 3
    GAME_OVER = 4
    PAUSED    = 5

##Pygame coordinate system:
##  +----> +x
##  |
##  v +y
class Direction:
    LEFT_UP    = 0
    RIGHT_UP   = 1
    RIGHT_DOWN = 2
    LEFT_DOWN  = 3

    @staticmethod
    def oppositeDirection(direction):
        opp_dir = {Direction.LEFT_UP:   Direction.RIGHT_DOWN,
                   Direction.LEFT_DOWN: Direction.RIGHT_UP,
                   Direction.RIGHT_DOWN:Direction.LEFT_UP,
                   Direction.RIGHT_UP  :Direction.LEFT_DOWN}
        return opp_dir[direction]
    @staticmethod
    def horizontalBounce(direction):
        h_bounce = {Direction.LEFT_DOWN :Direction.LEFT_UP,
                    Direction.LEFT_UP   :Direction.LEFT_DOWN,
                    Direction.RIGHT_DOWN:Direction.RIGHT_UP,
                    Direction.RIGHT_UP  :Direction.RIGHT_DOWN}
        return h_bounce[direction]
    @staticmethod
    def verticalBounce(direction):
        v_bounce = {Direction.LEFT_DOWN :Direction.RIGHT_DOWN,
                    Direction.LEFT_UP   :Direction.RIGHT_UP,
                    Direction.RIGHT_DOWN:Direction.LEFT_DOWN,
                    Direction.RIGHT_UP  :Direction.LEFT_UP}
        return v_bounce[direction]

TS = TILE_SIZE
class Ball:
    BALL_COUNTER = 0
    dir_map = {Direction.LEFT_DOWN :(-1,+1),
                   Direction.LEFT_UP   :(-1,-1),
                   Direction.RIGHT_DOWN:(+1,+1),
                   Direction.RIGHT_UP  :(+1,-1)}
    def __init__(self,x,y,direction):
        self.x         = x
        self.y         = y
        self.direction = direction
        self.stuck     = False
        self.id        = Ball.BALL_COUNTER
        Ball.BALL_COUNTER +=1

    def getNextPos(self,step_size):
        dx,dy = Ball.dir_map[self.direction]
        return (self.x+dx*step_size,self.y+dy*step_size)

    def setPos(self,x,y):
        self.x = x
        self.y = y
    def getPos(self):
        return (self.x,self.y)
    def getDir(self):
        return self.direction
    def setDir(self,direction):
        self.direction = direction
    def getRect(self):
        return pygame.rect.Rect(self.x,self.y,TILE_SIZE,TILE_SIZE)
    def __repr__(self):
        return "Ball %s (%s,%s) %s - %s"%(self.id,self.x,self.y,self.direction,self.stuck)

class Ray:
    HORIZONTAL = 0
    VERTICAL   = 1
    RAY_ONE    = 0
    RAY_TWO    = 1
    def __init__(self,dx,dy, origin_x, origin_y,ray_num):
        self.x_dir = dx ## how much it grows each update
        self.y_dir = dy
        self.length   = 0
        self.origin_x = origin_x
        self.origin_y = origin_y
        self.ray_num  = ray_num

    def drawSelfOn(self,surface):
        if self.x_dir == 0: ## vertical ray
            rect = (self.origin_x,self.origin_y,TILE_SIZE,self.length*self.y_dir)
            pygame.draw.rect(surface,RAY_COLOR[self.ray_num],rect)
        else:
            rect = (self.origin_x,self.origin_y,self.length*self.x_dir,TILE_SIZE)
            pygame.draw.rect(surface,RAY_COLOR[self.ray_num],rect)

    def advance(self):
        self.length += 1

    def retreat(self):
        self.length -= 1

    def endPoint(self):
        if self.x_dir == 0: ## vertical ray
            return (self.origin_x, self.origin_y + self.y_dir*self.length)
        else:
            return (self.origin_x + self.x_dir*self.length, self.origin_y)
    def getRect(self):
        if self.x_dir== 0: ##vertical ray
        ## funky weirdness with colliderect where it doesn't like comparing rects
        ## with negative widths and lengths..
            if self.y_dir < 0:
                r = pygame.rect.Rect(self.origin_x,self.origin_y-self.length,TILE_SIZE,self.length)
            else:
                r = pygame.rect.Rect(self.origin_x,self.origin_y,TILE_SIZE,self.length)
        else:
            if self.x_dir < 0:
                r = pygame.rect.Rect(self.origin_x-self.length,self.origin_y,self.length,TILE_SIZE)
            else:
                r = pygame.rect.Rect(self.origin_x,self.origin_y,self.length,TILE_SIZE)
        return r

class JGame:
    def __init__(self):
        ## Actual program initialization stuff
        self.ball_image        = pygame.image.load(BALL_FILE)
        pygame.display.set_icon(self.ball_image)
        pygame.display.set_caption("J*Ball")
        self.screen            = pygame.display.set_mode( SCREEN_SIZE)
        self.playarea          = pygame.surface.Surface( (TILE_SIZE*H_TILES,TILE_SIZE*V_TILES))
        self.header_surface    = pygame.surface.Surface( (HEADER_WIDTH, HEADER_HEIGHT))
        self.footer_surface    = pygame.surface.Surface( (FOOTER_WIDTH, FOOTER_HEIGHT))
        self.info_surface      = None ## shows stuff like "Paused", "Game over", etc
        self.background_image  = pygame.image.load(BACKGROUND_FILE)
        self.background_surface= self.generateBackground()
        self.clock             = pygame.time.Clock()
        self.font_small        = pygame.font.Font(None,FONT_SIZE_SMALL) ## use system-font for this
        self.compileCursors()
        ## per-game stuff
        self.resetGame()
        self.startLevel(25)   ## sort of a "demo" screen with balls randomly bouncing around
        self.state             = State.START_UP ## overwrite the state for now..
        self.score             = 0
        self.loadHighScore()

    def loadHighScore(self):
        try:
            with open("highscore","r") as f:
                score = int(f.readline())
                self.high_score = score
        except :
            print "Warning: Could not load high score!"
            self.high_score = 0

    def saveHighScore(self):
        try:
            with open("highscore","w") as f:
                f.write(str(self.high_score))
        except IOError:
            print "Warning: Could not save high score!"

    def startLevel(self,level):
        self.state             = State.RUNNING
        self.balls             = []
        self.spawnBalls(level+1)
        self.ball_time         = 0 ## time since the last time the balls were updated
        self.ball_update_counter=0 ## keeps track of how many times the updateBall() func was called
                                   ## balls move 4 times per collision check
        self.block_matrix      = Matrix(H_TILES,V_TILES)
        self.ray_placement     = Ray.VERTICAL
        self.ray1              = None
        self.ray2              = None
        self.rays              = level+1
        self.ray_time          = 0 ## time since the last time the rays were updated
        self.level             = level
        self.percent_done      = 0
        self.setCursor()

    def resetGame(self):
        self.score             = 0

    def compileCursors(self):
        data,mask = pygame.cursors.compile(HORIZONTAL_ARROW,black='.',white='X',xor='o')
        self.horizontal_cursor = ( (32,16), (16,8), data,mask)
        data,mask = pygame.cursors.compile(VERTICAL_ARROW,black='.',white='X',xor='0')
        self.vertical_cursor   = ( (16,32), (8,16), data,mask)

    def generateBackground(self):
        bground = pygame.surface.Surface( (TILE_SIZE*H_TILES,TILE_SIZE*V_TILES))
        for x in xrange(0,TILE_SIZE*H_TILES,TILE_SIZE):
            for y in xrange(0,TILE_SIZE*V_TILES,TILE_SIZE):
                bground.blit(self.background_image,(x,y))
        return bground

    def generateRandomBall(self):
            x         = random.randint(H_TILES/8,H_TILES*3/8)*2*TILE_SIZE
            y         = random.randint(V_TILES/8,V_TILES*3/8)*2*TILE_SIZE
            direction = random.randint(0,3)
            return (x,y,direction)

    def spawnBalls(self,n_balls):
        occupied_spots = []
        for ball in xrange(0,n_balls):
            x,y,d = self.generateRandomBall()
            while (x,y) in occupied_spots:
                x,y,d = self.generateRandomBall()
            occupied_spots.append( (x,y))
            b = Ball(x,y,d)
            self.balls.append(b)

    def start(self):
        self.spawnBalls(1)
        ##self.balls=[ Ball(0,0,0), Ball(32,0,0), Ball(64,0,0), Ball(96,0,0),Ball(128,0,0),Ball(160,0,0) ]
        ##self.balls=[ Ball(0,0,2), Ball(32,32,0) ]
        while self.state != State.EXITING:
            d_time= self.clock.tick(MAX_FPS)
            self.update(d_time)

    ## (x,y) in screen coordinates
    def placeRays(self,screen_x,screen_y):
        x = screen_x - BORDER_WIDTH
        y = screen_y - HEADER_HEIGHT

        if self.ray1 or self.ray2: ## currently a ray is being drawn
            return

        x = (x / TILE_SIZE) * TILE_SIZE
        y = (y / TILE_SIZE) * TILE_SIZE
        if self.ray_placement == Ray.HORIZONTAL:
            if not self.blockCol(x-1,y):
                self.ray1 = Ray(-1,0,x-1,y,Ray.RAY_ONE)
            if not self.blockCol(x,y):
                self.ray2 = Ray(+1,0,x  ,y,Ray.RAY_TWO)
        else:
            if not self.blockCol(x,y-1):
                self.ray1 = Ray(0,-1,x,y-1,Ray.RAY_ONE)
            if not self.blockCol(x,y):
                self.ray2 = Ray(0,+1,x,y,  Ray.RAY_TWO)

    def setCursor(self):
        if self.ray_placement == Ray.VERTICAL:
            pygame.mouse.set_cursor(*self.vertical_cursor)
        else:
            pygame.mouse.set_cursor(*self.horizontal_cursor)

    def toggleRayDirection(self):
        if self.ray_placement == Ray.VERTICAL:
            self.ray_placement = Ray.HORIZONTAL
        else:
            self.ray_placement = Ray.VERTICAL
        self.setCursor()

    def handleStartUpEvents(self,event):
        if (event.type == pygame.KEYDOWN) and (event.key == pygame.K_SPACE):
            self.state = State.RUNNING
            self.startLevel(1)

    def handleRunningEvents(self,event):
        if (event.type == pygame.KEYDOWN) and (event.key == pygame.K_p):
            self.state  = State.PAUSED
        elif event.type == pygame.MOUSEBUTTONDOWN:
            x,y = event.pos ## window coordinates!
            if event.button == 1: ## left click
                self.placeRays(x,y)
            elif event.button == 3: ## right click
                self.toggleRayDirection()

    def handleLevelWinEvents(self,event):
        if (event.type == pygame.KEYDOWN) and (event.key == pygame.K_SPACE):
            self.state = State.RUNNING
            self.level +=1
            self.startLevel(self.level)

    def handlePausedEvents(self,event):
        if (event.type == pygame.KEYDOWN) and (event.key == pygame.K_SPACE):
            self.state  = State.RUNNING

    def handleGameOverEvents(self,event):
        if (event.type == pygame.KEYDOWN) and (event.key == pygame.K_SPACE):
            self.state  = State.RUNNING
            self.level = 1
            self.resetGame()
            self.startLevel(self.level)

    def processEvents(self):
        events = pygame.event.get()
        for event in events:
            if ((event.type == pygame.QUIT) or
                ((event.type == pygame.KEYDOWN) and (event.key == pygame.K_ESCAPE))):
                self.state = State.EXITING

            if self.state == State.START_UP:
                self.handleStartUpEvents(event)
            elif self.state == State.RUNNING:
                self.handleRunningEvents(event)
            elif self.state == State.PAUSED:
                self.handlePausedEvents(event)
            elif self.state == State.LEVEL_WIN:
                self.handleLevelWinEvents(event)
            elif self.state == State.GAME_OVER:
                self.handleGameOverEvents(event)

    def drawBalls(self):
        for ball in self.balls:
            self.playarea.blit(self.ball_image,(ball.x,ball.y))

    def drawRays(self):
        if self.ray1:
            self.ray1.drawSelfOn(self.playarea)
        if self.ray2:
            self.ray2.drawSelfOn(self.playarea)

    def drawBlocks(self):
        m = self.block_matrix
        for row in xrange(0,V_TILES):
            for col in xrange(0,H_TILES):
                if m.get(row,col):
                    rect = (col*TILE_SIZE,row*TILE_SIZE,TILE_SIZE,TILE_SIZE)
                    pygame.draw.rect(self.playarea,BLOCK_COLOR,rect)

    def drawBackground(self):
        self.playarea.blit(self.background_surface,(0,0))

    def drawPlayArea(self):
        self.playarea.fill(PLAY_AREA_CLEAR_COLOR)
        self.drawBackground()
        self.drawBalls()
        self.drawRays()
        self.drawBlocks()

    def drawHeader(self,text):
        header_text_surface = self.font_small.render(text,True,TEXT_COLOR)
        header_text_x = (HEADER_WIDTH-header_text_surface.get_width())/2
        header_text_y = (HEADER_HEIGHT-header_text_surface.get_height())/2
        self.header_surface.fill(UI_CLEAR_COLOR)
        self.header_surface.blit(header_text_surface,(header_text_x,header_text_y))

    def drawFooter(self,text):
        footer_text_surface = self.font_small.render(text,True,TEXT_COLOR)
        footer_text_x = (FOOTER_WIDTH-footer_text_surface.get_width())/2
        footer_text_y = (FOOTER_HEIGHT-footer_text_surface.get_height())/2
        self.footer_surface.fill(UI_CLEAR_COLOR)
        self.footer_surface.blit(footer_text_surface,(footer_text_x,footer_text_y))

    def drawUIElements(self):
        header_txt, footer_txt = "",""
        if self.state == State.RUNNING:
            header_txt = "Lives: %s    Score: %s    High Score: %s    [P] - Pause"%(self.rays,int(self.score),int(self.high_score))
            footer_txt = "Area cleared: %s%%"%(self.percent_done)
        elif self.state == State.PAUSED:
            header_txt = "Game Paused"
            footer_txt = "Push SPACE to Resume"
        elif self.state == State.START_UP:
            header_txt = "J*BALL"
            footer_txt = "SPACE to Start"
        elif self.state == State.LEVEL_WIN:
            header_txt = "Lives: %s    Score: %s    High Score: %s    [P] - Pause"%(self.rays,int(self.score),int(self.high_score))
            footer_txt = "Level %s complete! - Push SPACE for next level"%self.level
        elif self.state == State.GAME_OVER:
            header_txt = "Game Over! Final Score: %s"%int(self.score)
            footer_txt = "Push SPACE to Start Over"

        self.drawHeader(header_txt)
        self.drawFooter(footer_txt)


    def drawGame(self):
        self.screen.blit(self.header_surface, (0,0))
        self.screen.blit(self.playarea,(BORDER_WIDTH,HEADER_HEIGHT))
        self.screen.blit(self.footer_surface, (0,HEADER_HEIGHT+V_TILES*TILE_SIZE))

    ## blockCollision - return true if a ball would collide at a block at some
    ## coordinate (x,y)
    def blockCol(self,x,y):
        row = y / TILE_SIZE
        col = x / TILE_SIZE
        if (x<0) or (y<0) or (x>=TILE_SIZE*H_TILES) or (y>=TILE_SIZE*V_TILES):
            return True
        return self.block_matrix.get(row,col)

    ## returns a list of all balls that the current ball may collide with
    ## if it moves
    def findNearbyBalls(self,ball):
        balls = []
        for cur_ball in self.balls:
            if not cur_ball == ball:
                balls.append(cur_ball)
        return balls


    def checkForObstacles(self,ball):
        x,y              = ball.getPos()   ## upper left corner
        nearby_balls = self.findNearbyBalls(ball)

        pos_list   = [(-1,-1),( 0,-1),(+1,-1),(+1, 0),
                      (+1,+1),( 0,+1),(-1,+1),(-1, 0)]

        obstacles = [False]*8

        for i in range(0,8):
            dx, dy = pos_list[i][0], pos_list[i][1]
            if not (x%TILE_SIZE) and not (y%TILE_SIZE): ## on a tile boundary
                if self.blockCol(x+dx*TILE_SIZE,y+dy*TILE_SIZE): ## hits the adjoining tiles
                    obstacles[i] = True

            ## Check whether there is another ball that'll interfere with this current
            ## ball once it moves
            next_x, next_y = x+dx*TILE_SIZE, y+dy*TILE_SIZE
            for current_ball in nearby_balls:
                other_x, other_y   = current_ball.getPos()
                if (other_x == next_x) and (other_y == next_y):
                    obstacles[i] = True
                    break
        return obstacles

    def resolveCol(self,ball):
        o = self.checkForObstacles(ball)

        ## the most basic test to see if there are no tiles in the path of the ball
        ## to act as collision
        #    0 1 2
        #    7   3
        #    6 5 4
        no_col_map   = {Direction.LEFT_UP   :[0,1,7],
                        Direction.RIGHT_UP  :[1,2,3],
                        Direction.RIGHT_DOWN:[3,4,5],
                        Direction.LEFT_DOWN :[5,6,7]}

        t = no_col_map[ball.getDir()]
        if (not o[t[0]]) and (not o[t[1]]) and (not o[t[2]]):
            return ## nothing to do here, no collision to speak of

        ## check to see if the ball is to be reflected horizontally
        ##  index 0 is the tile that the ball is reflecting off
        ##  index 2,3, and 4 must be clear if the ball is to go that way
        hor_col_map     = {Direction.LEFT_UP   :[1, 5,6,7],
                           Direction.RIGHT_UP  :[1, 3,4,5],
                           Direction.RIGHT_DOWN:[5, 3,2,1],
                           Direction.LEFT_DOWN :[5, 0,1,7]}

        t = hor_col_map[ball.getDir()]
        if o[t[0]] and (not o[t[1]]) and (not o[t[2]]) and (not o[t[3]]):
            ball.setDir( Direction.horizontalBounce(ball.getDir()))
            return

        ## check for vertical collision
        ## index 0 is the vert collision, index 2,3,4 must be clear for the ball to reflect
        vert_col_map    = {Direction.LEFT_UP   :[7, 1,2,3],
                           Direction.RIGHT_UP  :[3, 0,1,7],
                           Direction.RIGHT_DOWN:[3, 5,6,7],
                           Direction.LEFT_DOWN :[7, 3,4,5]}
        t = vert_col_map[ball.getDir()]
        if o[t[0]] and (not o[t[1]]) and (not o[t[2]]) and (not o[t[3]]):
            ball.setDir( Direction.verticalBounce(ball.getDir()))
            return

        ## see if there is a way for the ball to return back to where it came otherwise
        opp_dir = Direction.oppositeDirection(ball.getDir())
        t = no_col_map[opp_dir]
        if (not o[t[0]]) and (not o[t[1]]) and (not o[t[2]]):
            ball.setDir(opp_dir)
            return

        ## nowhere for the ball to go so..."
        ball.stuck = True

    ## gets all of the tiles that surround a ray, returns a list of
    ## (row,col)
    def emptyTilesAroundRay(self,ray):
        empty_tiles = []
        endx,endy = ray.endPoint()

        if ray.origin_x < endx:
            minx = ray.origin_x
            maxx = endx
        else:
            minx = endx
            maxx = ray.origin_x

        if ray.origin_y < endy:
            miny = ray.origin_y
            maxy = endy
        else:
            miny = endy
            maxy = ray.origin_y

        for x_coord in range(minx-TILE_SIZE,maxx+TILE_SIZE*2,TILE_SIZE):
            for y_coord in range(miny-TILE_SIZE,maxy+2*TILE_SIZE,TILE_SIZE):
                if not self.blockCol(x_coord,y_coord):
                    empty_tiles.append( (y_coord/TILE_SIZE,x_coord/TILE_SIZE) )
        return empty_tiles

    def ballInTiles(self,set_of_tiles):
        for tile in set_of_tiles:
            x,y = tile[1]*TILE_SIZE,tile[0]*TILE_SIZE
            rect = pygame.rect.Rect(x,y,TILE_SIZE,TILE_SIZE)
            for ball in self.balls:
                if ball.getRect().colliderect(rect):
                    return True
        return False

    def getEmptyNeighbors(self,set_of_tiles):
        neighbors = set([])
        for tile in set_of_tiles:
            for pos in [(-1,0),(+1,0),(0,-1),(0,+1)]:
                x = (tile[1]+pos[1])*TILE_SIZE
                y = (tile[0]+pos[0])*TILE_SIZE
                if not self.blockCol(x,y):
                    neighbors.add((tile[0]+pos[0],tile[1]+pos[1]))
        return neighbors

    def fillTiles(self,set_of_tiles):
        for tile in set_of_tiles:
            self.block_matrix.set(tile[0],tile[1],True)

    def fillInBlankSpace(self,ray):
        init_tiles = self.emptyTilesAroundRay(ray) ## 3x(tile length+2) array of candidate tiles
        while init_tiles:
            tile = init_tiles.pop()
            closed_set = set([])
            open_set = set([tile])                 ## tiles which have not been countered before
            contains_ball = False
            while open_set:
                if self.ballInTiles(open_set):     ##
                    ##closed_set = closed_set.union(open_set) ## bam, none of these tiles are good anymore.
                    contains_ball = True
                    break
                neighbors = self.getEmptyNeighbors(open_set) ## list of adjoining tiles (up/left/down/right)
                open_set_candidates = set([])
                for neighbor in neighbors:                   ## go through the neighbors and make
                    if not neighbor in closed_set.union(open_set): ## make sure these neighbors are unique
                        open_set_candidates.add(neighbor)
                closed_set = closed_set.union(open_set)      ## open set no longer has any use
                open_set = open_set_candidates               ## make a new set of tiles to explore
            if not contains_ball:
                self.fillTiles(closed_set)
            init_tiles_set = set(init_tiles)
            init_tiles_set.difference_update(closed_set)
            init_tiles = list(init_tiles_set)

    def resolveRayBlockCollisions(self,ray):
        endx,endy = ray.endPoint()
        if not self.blockCol(endx,endy):
            return
        else:
            ray.retreat()
            endx,endy = ray.endPoint()

        origin_x_tile = ray.origin_x/TILE_SIZE
        origin_y_tile = ray.origin_y/TILE_SIZE

        end_x_tile = endx/TILE_SIZE
        end_y_tile = endy/TILE_SIZE

        if ray.x_dir == 0: ## vertical ray
            for y_coord in range(origin_y_tile,end_y_tile+ray.y_dir,ray.y_dir):
                self.block_matrix.set(y_coord,origin_x_tile,True)
        else:
            for x_coord in range(origin_x_tile,end_x_tile+ray.x_dir,ray.x_dir):
                self.block_matrix.set(origin_y_tile,x_coord,True)

        self.previous_percent = self.percent_done
        self.fillInBlankSpace(ray)
        self.percent_done = self.calculateAreaClear()
        if self.percent_done >= WIN_PERCENT:
            self.state = State.LEVEL_WIN
            self.score += (WIN_PERCENT - self.previous_percent)*SCORE_MULTIPLIER
            self.score += (self.percent_done - WIN_PERCENT)*BONUS_MULTIPLIER
        else:
            self.score += (self.percent_done - self.previous_percent)*SCORE_MULTIPLIER

        if self.score > self.high_score:
            self.high_score = self.score

        ## Todo: Calculate the score here

        if ray.ray_num == Ray.RAY_ONE:
            self.ray1 = None
        else:
            self.ray2 = None

    def calculateAreaClear(self):
        return self.block_matrix.count(True)*100/(V_TILES*H_TILES)

    def updateRays(self,delta):
        self.ray_time += delta
        time_per_ray_update = 1000/RAY_SPEED
        while self.ray_time >= time_per_ray_update:
            if self.ray1:
                self.ray1.advance()
                self.resolveRayBlockCollisions(self.ray1)
            if self.ray2:
                self.ray2.advance()
                self.resolveRayBlockCollisions(self.ray2)
            self.ray_time -= time_per_ray_update

    def checkBallRayCollisions(self):
        for ball in self.balls:
            if self.ray1 and ball.getRect().colliderect(self.ray1.getRect()):
                self.ray1 = None
                self.rays -= 1
            if self.ray2 and ball.getRect().colliderect(self.ray2.getRect()):
                self.ray2 = None
                self.rays -= 1
        if self.rays <= 0:
            self.state = State.GAME_OVER
            self.saveHighScore()

    def updateBalls(self,delta_time):
        self.ball_time += delta_time
        ## dividing by 2 here since the ball is moved by 2 pixels per update
        ## and the ball speed is given in pixels per second
        time_per_ball_update = 1000/(BALL_SPEED/2)
        while self.ball_time >= time_per_ball_update:
            if self.ball_update_counter == 0:
                for ball in self.balls:
                    self.resolveCol(ball)
                self.ball_update_counter += 1
            else:
                self.ball_update_counter += 1
                if self.ball_update_counter > 3:
                    self.ball_update_counter = 0

            for ball in self.balls:
                if not ball.stuck:
                    ball.setPos( *ball.getNextPos(2) )
                else:
                    ball.stuck = False

            self.ball_time -= time_per_ball_update

    def updateGameLogic(self,delta):
        if self.state in [State.RUNNING, State.START_UP]:
            self.updateRays(delta)
            self.updateBalls(delta)
            self.checkBallRayCollisions()

    def update(self,delta_time):
        self.processEvents()

        time_per_update = 1000/MAX_FPS
        while delta_time >= time_per_update:
            self.updateGameLogic(time_per_update)
            delta_time -= time_per_update
        self.updateGameLogic(delta_time)

        self.drawPlayArea()
        self.drawUIElements()
        self.drawGame()
        pygame.display.flip()

def main():
    pygame.init()
    pygame.font.init()
    j = JGame()
    j.start()

if __name__ == '__main__':
    main()